package com.eeb.dropwizardmongo.example.resources;

import com.codahale.metrics.annotation.Timed;
import com.eeb.dropwizardmongo.example.api.User;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.mongojack.DBQuery;
import org.mongojack.DBUpdate;
import org.mongojack.JacksonDBCollection;

import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Map;

@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UsersResource {

    private JacksonDBCollection<User, String> users;

    public UsersResource(JacksonDBCollection<User, String> users) {
        this.users = users;
    }

    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getUsers(@QueryParam("firstName") String firstName,
                             @QueryParam("lastName") String lastName,
                             @QueryParam("email") String email,
                             @QueryParam("siteAdmin") Boolean siteAdmin,
                             @QueryParam("reputation") Integer reputation,
                             @QueryParam("badge") String badge,
                             @QueryParam("org") String org,
                             @QueryParam("assignment") String assignment,
                             @QueryParam("assignedOrg") String assignedOrg) throws UnsupportedEncodingException {
        DBQuery.Query query = DBQuery.empty();
        if (firstName != null)
            query = firstName.startsWith("-") ?
                    query.notEquals("firstName", firstName.substring(1)) :
                    query.is("firstName", firstName);
        if (lastName != null)
            query = lastName.startsWith("-") ?
                    query.notEquals("lastName", lastName.substring(1)) :
                    query.is("lastName", lastName);
        if (email != null)
            query = email.startsWith("-") ?
                    query.notEquals("email", email.substring(1)) :
                    query.is("email", email);
        if (siteAdmin != null)
            query = query.is("siteAdmin", siteAdmin);
        if (reputation != null)
            query = reputation < 0 ?
                    query.notEquals("reputation", reputation*-1) :
                    query.is("reputation", reputation);
        if (badge != null)
            query = badge.startsWith("-") ?
                    query.nor(DBQuery.all("badges", URLEncoder.encode(badge.substring(1), "UTF-8"))) :
                    query.all("badges", URLEncoder.encode(badge, "UTF-8"));
        if (org != null)
            query = org.startsWith("-") ?
                    query.nor(DBQuery.all("organizations", URLEncoder.encode(org.substring(1), "UTF-8"))) :
                    query.all("organizations", URLEncoder.encode(org, "UTF-8"));
        if (assignment != null)
            query = assignment.startsWith("-") ?
                    query.nor(DBQuery.all("assignments", URLEncoder.encode(assignment.substring(1), "UTF-8"))) :
                    query.all("assignments", URLEncoder.encode(assignment, "UTF-8"));
        if (assignedOrg != null)
            query = assignedOrg.startsWith("-") ?
                    query.nor(DBQuery.all("assignedOrgs", URLEncoder.encode(assignedOrg.substring(1), "UTF-8"))) :
                    query.all("assignedOrgs", URLEncoder.encode(assignedOrg, "UTF-8"));
        return Response.ok(users.find(query).toArray()).build();
    }

    @Path("/{user}")
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getUser(@PathParam("user") String userName) {
        return Response.ok(users.findOne(DBQuery.is("userName", userName))).build();
    }

    @Path("/{user}/orgs")
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public Response getOrgs(@PathParam("user") String userName) {
        return Response.ok(users.findOne(DBQuery.is("userName", userName)).getOrganizations()).build();
    }

    @POST
    @Timed
    public Response postUser(@Valid User user) throws URISyntaxException {
        URI uri = new URI("/" + user.getUserName());
        if (users.findOne(DBQuery.is("userName", user.getUserName())) == null)
            users.insert(user);
        return Response.created(uri).entity(users.findOne(DBQuery.is("userName", user.getUserName()))).build();
    }

    @PUT
    @Timed
    public Response putUser(@Valid User user) {
        return Response.status(405).entity("PUT to /users/{name} or POST to /users").build();
    }

    @Path("/{user}")
    @PUT
    @Timed
    public Response putUser(@PathParam("user") String userName,
                            @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("userName", userName);
        Map<String, Object> map = new ObjectMapper().readValue(json, new TypeReference<Map<String, Object>>() {
        });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() != null) {
                users.update(query, DBUpdate.set(entry.getKey(), entry.getValue()));
            }
        }
        return Response.ok(users.findOne(DBQuery.is("userName", userName))).build();
    }

    @Path("/{user}/orgs")
    @PUT
    @Timed
    public Response putOrg(@PathParam("user") String userName,
                           @Valid String json) throws IOException {
        DBQuery.Query query = DBQuery.is("userName", userName);
        User user = users.findOne(query);
        Map<String, Object> map = new ObjectMapper().readValue(json, new TypeReference<Map<String, Object>>() {
        });
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            user.addOrg(URLEncoder.encode((String) entry.getValue(), "UTF-8"));
        }
        users.update(query, DBUpdate.set("organizations", user.getOrganizations()));
        return Response.noContent().build();
    }

    @Path("/{user}")
    @DELETE
    @Timed
    public Response deleteUser(@PathParam("user") String userName) {
        users.findAndRemove(DBQuery.is("userName", userName));
        return Response.ok(users.find().toArray()).build();
    }

    @Path("/{user}/org/{organization}")
    @DELETE
    @Timed
    public Response deleteOrg(@PathParam("user") String userName,
                              @PathParam("organization") String organization) {
        DBQuery.Query orgQuery = DBQuery.is("userName", userName);
        users.findOne(orgQuery).removeOrg(organization);
        return Response.ok(users.findOne(orgQuery).getOrganizations()).build();
    }

}